class Compile {
  constructor(el, vm) {
    this.el = this.isElementNode(el) ? el : document.querySelector(el)
    this.vm = vm

    if (this.el) {
      let docFragment = this.nodeToFragment(this.el)

      this.compile(docFragment)
      this.el.appendChild(docFragment)
    }
  }

  isElementNode(node) {
    return node.nodeType === 1
  }

  isDirective(attrName) {
    return attrName.includes('v-')
  }

  nodeToFragment(el) {
    let docFragment = document.createDocumentFragment()
    let firstChild

    while (firstChild = el.firstChild){
      docFragment.appendChild(firstChild)
    }
    return docFragment
  }

  compile(docFragment) {
    let childNodes = docFragment.childNodes

    Array.prototype.slice.call(childNodes).forEach((node) => {
      if (this.isElementNode(node)) {
        // console.log('元素节点', node)
        this.compileElement(node)
        this.compile(node)
      } else {
        // console.log('文本节点', node)
        this.compileText(node)
      }
    })
  }

  compileElement(node) {
    let attrs = node.attributes
    // console.log(attrs)

    Array.prototype.slice.call(attrs).forEach((attr) => {
      // console.log(attr.name)
      // console.log(attr.value)
      let attrName = attr.name
      if (this.isDirective(attrName)) {
        let exp = attr.value
        let type = attrName.slice(2)
        // console.log(value)
        // console.log(node)
        CompileUtils[type](node, this.vm, exp)
      }
    })
  }

  compileText(node) {
    let exp = node.textContent
    // console.log(exp)
    let reg = /\{\{([^}]+)\}\}/g
    let expArr = reg.exec(exp)
    if (expArr) {
      // let expArr = reg.exec(exp)
      console.log(expArr)
      CompileUtils['text'](node, this.vm, expArr[1].trim())
    }
  }
}

CompileUtils = {
  model(node, vm, exp) {
    let updateFn = this.updater['modelUpdater']
    new Watcher(vm, exp, () => {
      updateFn && updateFn(node, this.getVal(vm, exp))
    })

    node.addEventListener('input', (e) => {
      let newValue = e.target.value
      this.setValue(vm, exp, newValue)
    })
    updateFn && updateFn(node, this.getVal(vm, exp))
  },
  text(node, vm, exp) {
    let updateFn = this.updater['textUpdater']
    new Watcher(vm, exp, () => {
      updateFn && updateFn(node, this.getVal(vm, exp))
    })
    updateFn && updateFn(node, this.getVal(vm, exp))
  },
  updater: {
    modelUpdater(node, value) {
      node.value = value
      // console.log(node)
    },
    textUpdater(node, value) {
      node.textContent = value
    }
  },
  getVal(vm, exp) {
    exp = exp.split('.')
    return exp.reduce((prev, next) => {
      return prev[next]
    }, vm.$data)
  },

  setValue(vm, exp, value) {
    exp = exp.split('.')

    return exp.reduce((prev, next, currentIndex) => {
      if (currentIndex === exp.length -1) {
        return prev[next] = value
      }

      return prev[next]
    }, vm.$data)
  }
}
